// BackupDatabase.cpp: Implementierung der Klasse BackupDatabase.
//
//////////////////////////////////////////////////////////////////////
#include "global.h"

#include "lncppapi.h"
#include "Logging.h"
#include "BackupDatabase.h"
#include "win32io.h"

#define BUFFER_SIZE   32768

//////////////////////////////////////////////////////////////////////
// Konstruktion/Destruktion
//////////////////////////////////////////////////////////////////////

BackupDatabase::BackupDatabase(LNDatabase& newDatabase)
{
	DEBUG("Constructor - BackupDatabase::BackupDatabase() - started");
	try {
		
		// get the c api handle
		dbHandle = newDatabase.GetDatabaseHandle(TRUE);

	} catch (LNSTATUS lnerror)
    {
		ERROR("Unhandled Exception thrown! ",lnerror);
	}
	
	dbBackupStarted=false;
	bApplyChangeInfoStarted=false;
	DEBUG("Constructor - BackupDatabase::BackupDatabase() - ended");
}

BackupDatabase::~BackupDatabase()
{
	DEBUG("Destructor - BackupDatabase::BackupDatabase() - started");
	CleanUp();
	DEBUG("Destructor - BackupDatabase::BackupDatabase() - ended");
}

// 
// Close all
void BackupDatabase::CleanUp() {
	DEBUG("BackupDatabase::CleanUp() - started!");

	if (bApplyChangeInfoStarted==true) {
		DEBUG("BackupDatabase::CleanUp() - Calling EndApplyChangeInfo!");
		EndApplyChangeInfo(APPLYEND_ABORT);
	}
	if (dbBackupStarted==true) {
		DEBUG("BackupDatabase::CleanUp() - Calling EndBackup!");
		EndBackup();
	}

	DEBUG("BackupDatabase::CleanUp() - ended!");
}

void BackupDatabase::MakeBackup(const char* destfile) {
	DEBUG("BackupDatabase::MakeBackup() - started!");
	if (IS_DEBUG()) {
		char buffer[256];
		strcpy(buffer,"Backupfile has the name: ");
		strcat(buffer,destfile);
		DEBUG(buffer);
	}
	
	try {
		// Get the full os filepath for the "OS" Copy
		GetFullPathName();
		if (IS_DEBUG()) {
			char buffer[256];
			strcpy(buffer,"Full pathname is: ");
			strcat(buffer,FullPath);
			DEBUG(buffer);
		}
		StartBackup();

		GetLogInfo();

		if(!Logged)
			DEBUG("BackupDatabase::MakeBackup() - Database is not logged and can not be recovered!");

		// making copies... with informing the server how far we are
		//
		makeDatabaseCopy(FullPath,destfile,true);
		
		// Stop the backup
		StopBackup();
		
		//
		ApplyChangeInfo(destfile);

		EndBackup();
	} catch (LNSTATUS lnerror)
    {
		ERROR("BackupDatabase::MakeBackup() - Unhandled Exception thrown! ",lnerror);
    }

	DEBUG("MakeBackup() - ended!");
}

void BackupDatabase::ApplyChangeInfo(const char* destfile) {
	DEBUG("BackupDatabase::ApplyChangeInfo() - started()");
	// This function will apply the changeInfo to the copy

	// Get size of change Info

	GetChangeInfoSize();

	// Start the apply for destfile
	StartApplyChangeInfo(destfile);

	// Apply all changes
	do {
		GetNextChangeInfo();
		ApplyNextChangeInfo();

	} while (FilledSize);


	EndApplyChangeInfo(0);

	DEBUG("BackupDatabase::ApplyChangeInfo() - ended()");
}

void BackupDatabase::EndApplyChangeInfo(DWORD flags) {
	// This function will terminate the application of the backup change information to a backup file.
	// Optional flag:
	//		APPLYEND_ABORT - Abort current change info application.

	DEBUG("BackupDatabase::EndApplyChangeInfo() - started()");
	STATUS   err = NOERROR;

	if (err = NSFBackupEndApplyChangeInfo(ApplyInfoContext,flags)) {
		WARN("BackupDatabase::EndApplyChangeInfo() - NSFBackupEndApplyChangeInfo faild! Ignoring!");

	}

	// Set inticator to ended
	bApplyChangeInfoStarted=false;
	DEBUG("BackupDatabase::EndApplyChangeInfo() - ended()");
}

void BackupDatabase::ApplyNextChangeInfo()
{
	DEBUG("BackupDatabase::ApplyNextChangeInfo() - started()");
	STATUS   err = NOERROR;

	if (err = NSFBackupApplyNextChangeInfo(ApplyInfoContext,
                                             0,
                                             InfoBuffer,
                                             FilledSize)) {
		ERROR("BackupDatabase::ApplyNextChangeInfo - NSFBackupGetNextChangeInfo failed!");
		throw (LNSTATUS) err;
	}

	DEBUG("BackupDatabase::ApplyChangeInfo() - ended()");
}

void BackupDatabase::GetNextChangeInfo() {
	DEBUG("BackupDatabase::GetNextChangeInfo - started()");
	STATUS   err = NOERROR;

	if (err = NSFBackupGetNextChangeInfo(dbHandle,
                                           BackupContext,
                                           0,
                                           InfoBuffer,
                                           BUFFER_SIZE,
                                           &FilledSize)) {

		ERROR("BackupDatabase::GetNextChangeInfo - NSFBackupGetNextChangeInfo failed!");
		throw (LNSTATUS) err;
	}

	DEBUG("BackupDatabase::GetNextChangeInfo - ended()");
}

void BackupDatabase::StartApplyChangeInfo(const char* destfile) {
	// This function will initiate the application of the backup change information 
	// to a copy of a database file taken during a backup operation.  
	
	DEBUG("BackupDatabase::StartApplyChangeInfo - started()");
	STATUS   err = NOERROR;

	 /* Initiate getting the change info */
	if (err = NSFBackupStartApplyChangeInfo(&ApplyInfoContext,
                                           (char*) destfile,
                                           0,
                                           InfoSizeLow,
                                           InfoSizeHigh))
	{
		ERROR("BackupDatabase::StartApplyChangeInfo - failed!");
		throw (LNSTATUS) err;
	}

	// Once successfully started NSFBackupEndApplyChangeInfo must be called to free allocated resources.
	bApplyChangeInfoStarted=true;

	DEBUG("BackupDatabase::StartApplyChangeInfo - ended()");
}

void BackupDatabase::GetChangeInfoSize() {
	// This function will return the total size in bytes of the 
	// change information for the copied database file.
	DEBUG("BackupDatabase::GetChangeInfoSize() - started()");
	STATUS   err = NOERROR;

	if (err = NSFBackupGetChangeInfoSize(dbHandle,
                                        BackupContext,
                                        0,
                                        &InfoSizeLow,
                                        &InfoSizeHigh)) {
		ERROR("BackupDatabase::GetChangeInfoSize() - NSFBackupGetChangeInfoSize failed!");
		throw (LNSTATUS) err;
	}
	DEBUG("BackupDatabase::GetChangeInfoSize() - ended()");
}


void BackupDatabase::StartBackup() {
	DEBUG("BackupDatabase::StartBackup() - started!");
	// This function will initiate the backup of a database and return a backup context to the caller.
	// The backup facility is only supported for local databases.
	STATUS   err = NOERROR;

	if (err = NSFBackupStart(dbHandle,		// Handle of the database
                            0L,				// reserved flag, is always zero!
                            &BackupContext,	// Pointer to a handle that will be set to 
											// the backup context to be used for subsequent calls.
                            &FileSizeLow,   
                            &FileSizeHigh))
   {
		ERROR("BackupDatabase::StartBackup() - NSFBackupStart failed!");
		throw (LNSTATUS) err;
   }
	dbBackupStarted=true;
	DEBUG("BackupDatabase::StartBackup() - ended!");
}


void BackupDatabase::StopBackup() {
	DEBUG("BackupDatabase::StopBackup() - started!");
	// This function is called when the raw backup copy of the database has 
	// completed at the operating system level, to stop recording the before-image 
	// change information for the database.
	STATUS   err = NOERROR;

	if (err = NSFBackupStop(dbHandle,BackupContext)) {
		WARN("BackupDatabase::StopBackup() - Calling NSFBackupStop failed! Ignoring!",err);
	}
	DEBUG("BackupDatabase::StopBackup() - ended!");

}

void BackupDatabase::EndBackup() {
	DEBUG("BackupDatabase::EndBackup() - started!");
	if (dbBackupStarted==false) {
		DEBUG("EndBackup() - Aborted.. dbBackupStarted was already false!");
		return;
	}
	// This function will deallocate the backup context 
	// that was obtained from NSFBackupStart once all recorded updates on 
	// the file should have been processed.

	// The only available option is:
	//		BACKUPEND_ABORT	  -  Abort current database backup.
	STATUS   err = NOERROR;
	if (err = NSFBackupEnd(dbHandle, BackupContext, BACKUPEND_ABORT)) {
		WARN("BackupDatabase::EndBackup() - NSFBackupEnd failed!");
	}

	dbBackupStarted=false;
	DEBUG("BackupDatabase::EndBackup() - ended!");
}

void BackupDatabase::GetLogInfo() {
	DEBUG("BackupDatabase::GetLogInfo() - started!");
	STATUS   err = NOERROR;

	// Check if database is logged.
	if (err = NSFDbGetLogInfo(dbHandle, 0L, &Logged, &LogID, &DbIID, &LogExtent))
	{
		ERROR("BackupDatabase::GetLogInfo() - Calling NSFDbGetLogInfo failed!");
		throw (LNSTATUS) err;
	}
	DEBUG("BackupDatabase::GetLogInfo() - ended!");
}

const char* BackupDatabase::GetFullPathName() {
	DEBUG("BackupDatabase::GetFullPathName() - started!");
	// Get the full path name of the database! Not relative to the data!!
	STATUS   err = NOERROR;
	if (err = NSFDbPathGet(dbHandle, NULL, FullPath))
	{
		ERROR("BackupDatabase::GetFullPathName() failed!");
		throw err;
	}
	DEBUG("BackupDatabase::GetFullPathName() - ended!");
	return FullPath;
}

bool BackupDatabase::makeDatabaseCopy(const char* sourcefile, const char* destfile, bool overwrite)
{
	DEBUG("BackupDatabase::makeDatabaseCopy() - started!");
	// makeing a copy of a database
	// In the meanwhile we tell domino, how far we are with a HighWaterMark context

	// initialise the file handles
	int      srcfd, dstfd;

	STATUS   err = NOERROR;

	// Open the database file at the OS level
	if (err = SysFileOpenRead(sourcefile, &srcfd))
	{
		ERROR("BackupDatabase::makeDatabaseCopy() - Failed to open Source file");
		DEBUG("BackupDatabase::makeDatabaseCopy() - ended! Returning false!");
		return false;
	}

	// Move file pointer to begining of file 
	if (err = SysFileSeek(srcfd, 0, 0))
	{
      ERROR("BackupDatabase::makeDatabaseCopy() - FileSeek failed to go to file beginn!");
      SysFileClose(srcfd);
    }

	// if we want overwrite, try to delete the destination
	if (overwrite==true)
		SysFileDelete(destfile);

	// Create the destination file - do not overwrite it if it already exists */
	if (err = SysFileCreate(destfile, &dstfd))
	{
	  ERROR("BackupDatabase::makeDatabaseCopy() - Creation of destination file failed!");
      SysFileClose(srcfd);
	  DEBUG("BackupDatabase::makeDatabaseCopy() - ended! Returning false!");
	  return false;
	}

	HANDLE   hBuffer; // <- Bufferhandle

	// Allocate memory the notes way (os intependet) for read/write
	if (err = OSMemAlloc(MEM_SHARE, BUFFER_SIZE, &hBuffer))
	{
		ERROR("BackupDatabase::makeDatabaseCopy() - Couldn't get enough memory!");
		SysFileClose(dstfd);
		SysFileDelete(destfile);
		SysFileClose(srcfd);
		DEBUG("BackupDatabase::makeDatabaseCopy() - ended! Returning false!");
		return false;
	}

	char     *Buffer; // define a pointer to the buffer

	// Lock buffer for other threads
	Buffer = OSLock(char, hBuffer);

	DWORD    BytesLeft; // contains how much data has to be processed
   
	// Start the copy of the file
	// check how much we want to read
	if ((MAXDWORD - FileSizeHigh) >= FileSizeLow)
	{
		BytesLeft = FileSizeHigh + FileSizeLow;
		FileSizeLow = 0;
	}
	else
	{
		BytesLeft = FileSizeHigh;
	}	

	DWORD    SrcPositionLow = 0;
	DWORD    SrcPositionHigh = 0;

	// Repeate until the whole file is copies
	do
	{
		DWORD ReadCount = 0;
		while (BytesLeft)
		{
			// Read until nothing left or how big the buffer ist
			DWORD BytesRead = MIN(BUFFER_SIZE, BytesLeft);

			if (err = SysFileRead(srcfd, Buffer, BytesRead)) {
				WARN("FileRead Error! Shouldnt occur!");
				break;
			}



			if ((MAXDWORD - SrcPositionLow) < BytesRead)
			{
				SrcPositionHigh++;
				SrcPositionLow = (BytesRead - (MAXDWORD - SrcPositionLow)) - 1;
			}
			else
			{
				SrcPositionLow += BytesRead;
			}

			// Tell domino, how far we are ... thats why we make the whole 
			// action and not only use filecopy

         
			if ((++ReadCount % 10) == 0)
				NSFBackupSetHighWaterMark(dbHandle, 
					                      BackupContext,
						                  SrcPositionLow,
							              SrcPositionHigh);

			if (err = SysFileWrite(dstfd, Buffer, BytesRead)) {
				WARN("Unexpected Write error!");
				break;
			}

			// Now not so much files availabe
			BytesLeft -=BytesRead;
		} // while (no bytes left)

		// Write the last bytes if needed
		if (FileSizeHigh > 0)
		{
			BytesLeft = MAXDWORD;
			FileSizeHigh--;
		}
		else
		{
			BytesLeft = FileSizeLow;
			FileSizeLow = 0;
		}

	} while (BytesLeft && !(err));

	/* Close our copy */
	SysFileClose(dstfd);

	// was there any error tell it!
	if (err==NOERROR) {
		DEBUG("BackupDatabase::makeDatabaseCopy() - ended! Returning true!");
		return true;
	} else {
		return false;
		DEBUG("BackupDatabase::makeDatabaseCopy() - ended! Returning false!");
	}
}

